package cn.kuporo.seckill.order.application.place.imnpl;

import cn.hutool.core.util.BooleanUtil;
import cn.kuporo.seckill.common.cache.distribute.DistributedCacheService;
import cn.kuporo.seckill.common.constants.MQConstants;
import cn.kuporo.seckill.common.constants.RedisConstants;
import cn.kuporo.seckill.common.exception.ErrorCode;
import cn.kuporo.seckill.common.exception.SeckillException;
import cn.kuporo.seckill.common.model.dto.SeckillGoodsDTO;
import cn.kuporo.seckill.common.model.enums.PlaceOrderType;
import cn.kuporo.seckill.common.model.message.TxMessage;
import cn.kuporo.seckill.common.utils.SnowFlakeFactory;
import cn.kuporo.seckill.dubbo.interfaces.goods.SeckillGoodsDubboService;
import cn.kuporo.seckill.order.application.enums.DecrementsStockResult;
import cn.kuporo.seckill.order.application.model.command.SeckillOrderCommand;
import cn.kuporo.seckill.mq.MessageSenderService;
import cn.kuporo.seckill.order.application.place.SeckillPlaceOrderService;
import cn.kuporo.seckill.order.domain.model.entity.SeckillOrder;
import cn.kuporo.seckill.order.domain.service.SeckillOrderDomainService;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author: kuporo
 * @since: 2025/7/10
 * @description: 同步下单
 */
@Service
@ConditionalOnProperty(name = "place.order.type", havingValue = "lua")
@RequiredArgsConstructor
@Slf4j
public class SeckillPlaceOrderLuaService implements SeckillPlaceOrderService {
    private final SeckillOrderDomainService seckillOrderDomainService;
    private final DistributedCacheService distributedCacheService;
    private final MessageSenderService messageSenderService;


    @DubboReference
    private SeckillGoodsDubboService seckillGoodsDubboService;
    @Override
    public Long placeOrder(Long userId, SeckillOrderCommand seckillOrderCommand) {
        Long goodsId = seckillOrderCommand.getGoodsId();
        if (goodsId == 294594355652595712L){
            return 1111L;
        }
        SeckillGoodsDTO seckillGoodsDTO = seckillGoodsDubboService.getSeckillGoods(goodsId, seckillOrderCommand.getVersion());
        // 检测商品
        this.checkSeckillGoods(seckillOrderCommand, seckillGoodsDTO);
        // 默认没有出现异常
        boolean exception = false;
        // 生成全局事务编号
        long txNo = SnowFlakeFactory.getSnowFlakeFromCache().nextId();
        // 构建商品库存key
        String key = RedisConstants.getKey(RedisConstants.GOODS_ITEM_STOCK_KEY_PREFIX, seckillOrderCommand.getGoodsId());
        Long decrementResult = 0L;
        try {
            // 如果从Redis获取到的限购信息为null，则说明商品已经下线
            Long limitObj = distributedCacheService.getObject(RedisConstants.getKey(RedisConstants.GOODS_ITEM_LIMIT_KEY_PREFIX, goodsId), Long.class);
            if (limitObj == null){
                throw new SeckillException(ErrorCode.GOODS_OFFLINE);
            }
            if (limitObj.intValue() < seckillOrderCommand.getQuantity()){
                throw new SeckillException(ErrorCode.BEYOND_LIMIT_NUM);
            }
            decrementResult = distributedCacheService.decrementByLua(key, seckillOrderCommand.getQuantity());
            this.checkResult(decrementResult);
        }catch (Exception e){
            log.error("SeckillPlaceOrderLuaService|下单异常|参数:{}|异常信息:{}", JSONObject.toJSONString(seckillOrderCommand), e.getMessage());
            exception = true;
            //将内存中的库存增加回去
            if (Objects.equals(decrementResult, DecrementsStockResult.SUCCESS.getResult())){
                distributedCacheService.incrementByLua(key, seckillOrderCommand.getQuantity());
            }
        }
        // 发送事务消息
        messageSenderService.sendMessageInTransaction(this.getTxMessage(MQConstants.TOPIC_TX_MSG, txNo, userId, PlaceOrderType.LUA, exception, seckillOrderCommand, seckillGoodsDTO, 0, seckillOrderCommand.getOrderTaskId()), null);
        return txNo;
    }


    /**
     * 检查Lua执行结果
     */
    private void checkResult(Long result) {
        DecrementsStockResult decrementsStockResult = DecrementsStockResult.valueOf(result);
        switch (decrementsStockResult){
            case STOCK_NOT_EXISTS -> throw new SeckillException(ErrorCode.STOCK_IS_NULL);
            case STOCK_LT_ZERO -> throw new SeckillException(ErrorCode.STOCK_LT_ZERO);
        }
    }
    @Override
    @Transactional
    public void saveOrderInTransaction(TxMessage txMessage) {
        try {
            Boolean submitTransaction = distributedCacheService.hasKey(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()));
            if (Boolean.TRUE.equals(submitTransaction)){
                log.info("saveOrderInTransaction|已经执行过本地事务|{}", txMessage.getTxNo());
                return;
            }
            //构建订单
            SeckillOrder seckillOrder = this.buildSeckillOrder(txMessage);
            //保存订单
            seckillOrderDomainService.saveSeckillOrder(seckillOrder);
            //保存事务日志
            distributedCacheService.put(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()), txMessage.getTxNo(), RedisConstants.TX_LOG_EXPIRE_DAY, TimeUnit.DAYS);
        }catch (Exception e){
            log.error("saveOrderInTransaction|异常|{}", e.getMessage());
            distributedCacheService.delete(RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()));
            this.rollbackCacheStock(txMessage);
            throw e;
        }
    }

    /**
     * 回滚缓存库存
     * @param txMessage
     */
    private void rollbackCacheStock(TxMessage txMessage) {
        // 扣减过缓存库存
        if (BooleanUtil.isFalse(txMessage.getException())){
            String luaKey = RedisConstants.getKey(RedisConstants.ORDER_TX_KEY, txMessage.getTxNo()).concat(RedisConstants.LUA_SUFFIX);
            Long result = distributedCacheService.checkExecute(luaKey, RedisConstants.ORDER_TX_LOG_EXPIRE_SECONDS);
            if (result.equals(RedisConstants.CHECK_RECOVER_STOCK_HAS_EXECUTE)){
                log.info("handlerCacheStock|已经执行过恢复缓存库存的方法|{}", JSONObject.toJSONString(txMessage));
                return;
            }
            // 只有分布式锁方式和Lua脚本方法才会扣减缓存中的库存
            String key = RedisConstants.getKey(RedisConstants.GOODS_ITEM_STOCK_KEY_PREFIX, txMessage.getGoodsId());
            distributedCacheService.increment(key, txMessage.getQuantity());
        }
    }
}
